package org.linlinjava.litemall.web.aop;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.lang.Assert;
import com.alibaba.fastjson.JSON;
import com.google.common.collect.Lists;
import lombok.SneakyThrows;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.linlinjava.litemall.core.exception.ServiceException;
import org.linlinjava.litemall.core.util.IPGeolocation;
import org.linlinjava.litemall.core.util.WeChatBot.WeChatBotUtils;
import org.linlinjava.litemall.db.config.AppConfig;
import org.linlinjava.litemall.web.annotation.LogAnno;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.ui.ModelMap;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.reflect.Method;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.Date;
import java.util.List;

/**
 * @author ：liushichang
 * @date ：Created in 2019/4/24 17:30
 * @description：日志记录
 */
@Component
@Aspect
public class WebLogAop {

    private static Logger logger = LoggerFactory.getLogger(WebLogAop.class);

    public static final String WECHAT_FAIL = "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=331a7ea0-399e-441a-8f61-d079e2eeae69";

    private final AppConfig appConfig;
    @Autowired
    public WebLogAop(AppConfig appConfig) {
        this.appConfig = appConfig;
    }

    @SneakyThrows
    @AfterThrowing(value = "execution(* org.linlinjava.litemall.web.controller.*.*(..))", throwing = "exception")
    public void afterThrowing(JoinPoint jp, Exception exception) {
        String name = jp.getSignature().getName();
        logger.info(name+"方法抛异常了，异常-> {}", exception.getMessage());

        //如果是断言返回的失败报错就不用发通知
        if(exception instanceof IllegalArgumentException|| exception instanceof ServiceException)return;

        // 接收到请求，记录请求内容
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();

        // 获取客户端的 IP 地址
        String ipAddress = request.getHeader("X-Real-IP");
        if (ipAddress == null || ipAddress.isEmpty()) {
            ipAddress = request.getHeader("X-Forwarded-For");
        }
        if (ipAddress == null || ipAddress.isEmpty()) {
            ipAddress = request.getRemoteAddr();
        }

        WeChatBotUtils weChatBot = new WeChatBotUtils(WECHAT_FAIL, true);
        String markdownMsg = "Lumiere-BUG警报\n" +
                "    >告警来自：<font color=\"warning\">Web</font >\n" +
                "    >BUG接口：<font color=\"warning\">"+request.getRequestURI()+"</font>\n" +
                "    >环境来源：<font color=\"warning\">"+appConfig.getSource()+"</font >\n" +
                "    >请求方法名：<font color=\"warning\">"+jp.getSignature().getName()+"</font >\n" +
                "    >请求类型：<font color=\"warning\">"+request.getMethod()+"</font>\n" +
                "    >请求IP：<font color=\"warning\">"+ipAddress+"</font>\n" +
                "    >请求地区：<font color=\"warning\">"+ IPGeolocation.getIPGeolocation(ipAddress)+"</font>\n" +
                "    >入　参：<font color=\"warning\">"+ jp.getArgs() +"</font>\n" +
                "    >日　期：<font color=\"warning\">"+ DateUtil.formatDate(DateUtil.date())+"</font>\n" +
                "    >时　间：<font color=\"comment\">"+ DateUtil.format(new Date(), "a")+ DateUtil.formatTime(DateUtil.date())+"</font>\n" +
                "    >BUG信息：<font color=\"info\">"+exception+"</font>\n" +
                ">详细错误：<font color=\"comment\">" + exception.getMessage() + "</font>\n";
        StringWriter sw = new StringWriter();
        exception.printStackTrace(new PrintWriter(sw));
        String stackTrace = sw.toString();
        markdownMsg += ">堆栈信息：\n```\n" + stackTrace + "\n```";
        weChatBot.sendMarKDownMsg(markdownMsg);
    }

    @Around("@annotation(org.linlinjava.litemall.web.annotation.LogAnno)")
    public Object doAround(ProceedingJoinPoint pjp) throws Throwable{
        if(!(pjp.getSignature() instanceof MethodSignature)){
            return pjp.proceed();
        }
        MethodSignature methodSignature = (MethodSignature) pjp.getSignature();
        Method method = AopUtils.getMostSpecificMethod(methodSignature.getMethod(), pjp.getTarget().getClass());
        LogAnno logInfo = method.getAnnotation(LogAnno.class);
        long startTime = System.currentTimeMillis();
        Object result = null;
        try{
            result = pjp.proceed();
        }catch(Throwable e){
            if(logInfo.logError()){
                printLog(method,logInfo,pjp.getArgs(),e,startTime);
            }
            throw e;
        }
        printLog(method,logInfo,pjp.getArgs(),result,startTime);
        return result;
    }

    private void printLog(Method method, LogAnno logInfo, Object[] args, Object result, long startTime) {
        String logBody = builderLog(method,logInfo,result instanceof Throwable);
        List<Object> param = filterReqParam(args);

        if(logInfo.logTime()){
            logger.info(logBody, JSON.toJSONString(param),System.currentTimeMillis()-startTime,result);
        }else{
            logger.info(logBody,JSON.toJSONString(param),result);
        }

    }


    private List<Object> filterReqParam(Object[] args) {
        List<Object> param = Lists.newArrayList();
        for(Object arg : args){
            if(arg instanceof ServletRequest
                    ||arg instanceof ServletResponse
                    ||arg instanceof HttpSession
                    ||arg instanceof ModelMap
                    ||arg instanceof ModelAndView
                    ||arg instanceof MultipartFile
                    ||arg instanceof MultipartFile[]){
                continue;
            }
            param.add(arg);
        }
        return param;
    }

    private String builderLog(Method method, LogAnno logInfo, boolean isException) {
        StringBuilder body = new StringBuilder(builderMethodBody(method,logInfo));
        body.append(" param:{}");
        if(logInfo.logTime()){
            body.append(",time:{}ms");
        }
        if(isException){
            body.append(",exception:");
        }else{
            body.append(",result:{}");
        }
        return body.toString();
    }


    private String builderMethodBody(Method method, LogAnno logInfo) {
        StringBuilder builder = new StringBuilder();
        if (!StringUtils.isEmpty(logInfo.value())) {
            builder.append(logInfo.value());
        }else{
            builder.append(method.getDeclaringClass().getSimpleName()).append(".").append(method.getName()).append("()");
        }
        return builder.toString();
    }





}
